// +FHDR------------------------------------------------------------
//                 Copyright (c) 2024 NOVAUTO.
//                       ALL RIGHTS RESERVED
// -----------------------------------------------------------------
// Filename      : sync_hand_fifo_ram.v
// Author        : ICer
// Created On    : 2024-01-04 11:18
// Last Modified : 2024-01-06 00:18 by ICer
// -----------------------------------------------------------------
// Description:
//
//
// -FHDR------------------------------------------------------------


module sync_hand_fifo_ram #(
    //parameter
    parameter WIDTH = 8,
    parameter DEPTH = 8
)( /*AUTOARG*/
   // Outputs
   data_in_ready, data_out_valid, data_out,
   // Inputs
   clk, rst_n, data_in_valid, data_in, data_out_ready
   );

// ----------------------------------------------------------------
// Interface declare
// ----------------------------------------------------------------
input             clk;
input             rst_n;

input             data_in_valid;
input [WIDTH -1:0]data_in;
output            data_in_ready;

output            data_out_valid;
output[WIDTH -1:0]data_out;
input             data_out_ready;

// ----------------------------------------------------------------
// Wire declare
// ----------------------------------------------------------------
localparam RAM_DEPTH  = DEPTH - 2;
localparam ADDR_W     = $clog2(RAM_DEPTH);
localparam ADDR_W_EX  = ADDR_W + 1;
localparam DEPTH_W    = $clog2(DEPTH);
localparam DEPTH_W_EX = DEPTH_W + 1;

localparam ADDR_W_EX_0 = {ADDR_W_EX{1'b0}};
localparam ADDR_W_EX_1 = {{(ADDR_W_EX-1){1'b0}}, 1'b1};


reg  [ADDR_W_EX -1:0]ram_cnt;
reg  [ADDR_W    -1:0]waddr;
wire [ADDR_W    -1:0]raddr;
wire [WIDTH     -1:0]wdata;
wire [WIDTH     -1:0]rdata;
wire                 wenc;
wire                 renc = 1'b1;
reg                  rvld;


reg [WIDTH -1:0]out_buf0,    out_buf1;
wire[WIDTH -1:0]out_buf0_d,  out_buf1_d;
wire            out_buf0_en, out_buf1_en;

reg             out_buf0_power, out_buf1_power;
wire            out_buf0_power_d, out_buf1_power_d;


wire data_in_hand_en  = (data_in_valid && data_in_ready);
wire data_out_hand_en = (data_out_valid && data_out_ready);

wire ram_empty        = (ram_cnt == {ADDR_W_EX{1'b0}});
wire ram_full         = (ram_cnt >= RAM_DEPTH);

// ----------------------------------------------------------------
// AUTO declare
// ----------------------------------------------------------------
/*AUTOOUTPUT*/
/*AUTOINPUT*/
/*AUTOWIRE*/

// ----------------------------------------------------------------
// ram inst
// ----------------------------------------------------------------
/*dual_port_ram AUTO_TEMPLATE(
.wclk(clk),
.rclk(clk),
.DEPTH(RAM_DEPTH),
);
*/
dual_port_ram #(/*AUTOINSTPARAM*/
                // Parameters
                .DEPTH                  (RAM_DEPTH),             // Templated
                .WIDTH                  (WIDTH))
u_ram(
      .rdata                            (rdata[WIDTH-1:0]),
      /*AUTOINST*/
      // Inputs
      .wclk                             (clk),                   // Templated
      .wenc                             (wenc),
      .waddr                            (waddr[$clog2(RAM_DEPTH)-1:0]),
      .wdata                            (wdata[WIDTH-1:0]),
      .rclk                             (clk),                   // Templated
      .renc                             (renc),
      .raddr                            (raddr[$clog2(RAM_DEPTH)-1:0]));

// ----------------------------------------------------------------
// ram status
// ----------------------------------------------------------------
wire ram_can_read     = (ram_cnt > ADDR_W_EX_0);
wire ram_can_pre_read = (ram_cnt > ADDR_W_EX_1);
reg  ram_can_pre_read_ff;
reg  ram_can_read_ff;

always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    ram_can_pre_read_ff <= 1'b0;
  end 
  else begin
    ram_can_pre_read_ff <= ram_can_pre_read;
  end
end

always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    ram_can_read_ff <= 1'b0;
  end 
  else begin
    ram_can_read_ff <= ram_can_read;
  end
end

// ----------------------------------------------------------------
// ram renc && raddr
// ----------------------------------------------------------------
reg [ADDR_W -1:0]raddr_q;
wire[ADDR_W -1:0]raddr_d;
wire             raddr_en;

wire             ram_to_buffer_en;

assign raddr_d  = (raddr_q >= RAM_DEPTH - 1'b1) ? {ADDR_W{1'b0}} : raddr_q + 1'b1;
assign raddr_en = ram_to_buffer_en;

always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    raddr_q <= {ADDR_W{1'b0}};
  end 
  else if(raddr_en) begin
    raddr_q <= raddr_d;
  end
end

assign raddr = raddr_en ? raddr_d : raddr_q;

reg raddr_en_ff;
always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    raddr_en_ff <= 1'b0;
  end 
  else begin
    raddr_en_ff <= raddr_en;
  end
end

wire ram_out_power = raddr_en_ff ? ram_can_pre_read_ff : ram_can_read_ff;

// ----------------------------------------------------------------
// ram wenc && waddr
// ----------------------------------------------------------------
assign wenc = data_in_hand_en && ((out_buf0_power && out_buf1_power) || !ram_empty);

always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    waddr <= {ADDR_W{1'b0}};
  end 
  else if(wenc) begin
    if(waddr >= RAM_DEPTH - 1'b1) 
      waddr <= {ADDR_W{1'b0}};
    else                      
      waddr <= waddr + 1'b1;
  end
end

assign wdata = data_in;

// ----------------------------------------------------------------
// ram cnt logic
// ----------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    ram_cnt <= {ADDR_W_EX{1'b0}};
  end 
  else if(wenc ^ raddr_en) begin
    if(wenc) ram_cnt <= ram_cnt + 1'b1;
    else     ram_cnt <= ram_cnt - 1'b1;
  end
end

// ----------------------------------------------------------------
// out data buffer0/1
// ram -> out_buf0 -> out_buf1
// ----------------------------------------------------------------
//buffer1, send-buffer
assign out_buf1_en       = data_out_hand_en || (out_buf1_power == 1'b0);

wire out_buf1_from_buf0  = out_buf0_power;
wire out_buf1_from_ram   = (!out_buf0_power) && ram_out_power;
wire out_buf1_from_input = (!out_buf0_power) && ram_empty && data_in_hand_en;

assign out_buf1_d        = out_buf1_from_buf0 ? out_buf0:
                           out_buf1_from_ram  ? rdata :
                                                data_in;
assign out_buf1_power_d  = (out_buf1_from_buf0 || out_buf1_from_ram || out_buf1_from_input);

//buffer0
assign out_buf0_en       = data_out_hand_en || (out_buf0_power == 1'b0);

wire out_buf0_from_ram   = out_buf1_power && !data_out_ready && ram_out_power;
wire out_buf0_from_input = out_buf1_power && !data_out_ready && ram_empty && data_in_hand_en;

assign out_buf0_d        = out_buf0_from_ram ? rdata : data_in;
assign out_buf0_power_d  = (out_buf0_from_ram || out_buf0_from_input);

//ram out
assign ram_to_buffer_en  = (out_buf0_en && out_buf0_from_ram) ||
                           (out_buf1_en && out_buf1_from_ram) ;

//reg
always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    out_buf1_power <= 1'b0;
  end 
  else if(out_buf1_en)begin
    out_buf1_power <= out_buf1_power_d;
  end
end
always @(posedge clk) begin
  if(out_buf1_en)begin
    out_buf1 <= out_buf1_d;
  end
end

always @(posedge clk or negedge rst_n) begin
  if(!rst_n) begin
    out_buf0_power <= 1'b0;
  end 
  else if(out_buf0_en)begin
    out_buf0_power <= out_buf0_power_d;
  end
end
always @(posedge clk) begin
  if(out_buf0_en)begin
    out_buf0 <= out_buf0_d;
  end
end

// ----------------------------------------------------------------
// out logic
// ----------------------------------------------------------------
wire [DEPTH_W_EX -1:0]fifo_cnt = ram_cnt + out_buf0_power + out_buf1_power;

assign data_in_ready  = fifo_cnt < DEPTH;
assign data_out_valid = out_buf1_power;
assign data_out       = out_buf1;

endmodule
// Local Variables:
// verilog-auto-inst-param-value:t
// verilog-library-directories:(".")
// verilog-library-extensions:(".v")
// End:

